Using the Google Maps API

A lot of the Google Maps geographic functionality can be got at programmatically! This can be really useful for getting information about a place, even when you don't want to show it on a map.

Here we will look at the 'Google Places' web API, and along the way we'll learn how to use web service APIs in general. We saw a little bit of this with text collation, so this time we'll look a little more carefully at how the web part works.

The first thing we will need is a Python library for talking to the Web. The best one out there at the moment is the 'requests' library, which doesn't come standard in Python but which you probably already have installed. If this doesn't work, go to a command / terminal window and type

pip install requests

or

pip3 install requests

if you are on a Mac.


In [1]:
import requests

You have some place name, and you want to find out where it is. We do this using two API functions provided by Google, first to get the ID of the place, and then to get more information about it.

Google, like Zotero, requires an API key to use its web API. This is because Google sets limits to how much you can use it for free. The limits should plenty for a normal person's use, though.

For the time being, you can use the API key that I have put in ILIAS. If you are going to do your own work with Maps, though, then you should go to http://developers.google.com/ and sign up as a developer. You'll then need to create a project, and in that project go to 'API keys & auth' -> and set up access for the API you want to use. The one we're using today is the Google Places API Web Service. You'll then need to go to 'Credentials' to make your API key. If you need further help with the options, then talk to me!

Google's documentation for the Places API is here: https://developers.google.com/places/webservice/intro

and today we'll be using the Place Search and Place Details functions.


In [2]:
search_endpoint = 'https://maps.googleapis.com/maps/api/place/textsearch/json'
search_params = {
    'query': 'Länggass Stübli',
    'key': 'AIzaSyCNx-klDCfhopV6W_QPFZ0iwv5sp1J0XwQ',
    'language': 'en'
    }


r = requests.get( search_endpoint, params=search_params)
r.json()   # See what we got


Out[2]:
{'html_attributions': [],
 'results': [{'formatted_address': 'Muesmattstrasse 46, 3012 Bern, Switzerland',
   'geometry': {'location': {'lat': 46.954447, 'lng': 7.429129}},
   'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png',
   'id': '2388120ff47e1457ecd9b06c87e017e6d4e62683',
   'name': 'Restaurant Länggass Stübli da Massimo',
   'opening_hours': {'open_now': True, 'weekday_text': []},
   'photos': [{'height': 416,
     'html_attributions': [],
     'photo_reference': 'CnRnAAAAV7_fsCPmBntygrU-UnJh2SYStbcU8YeANgtGF5TY4MC5WXRRcNUUZ2nPM7BuWeimkb1Tg65oBCHDV7m6DdSiMZC8XVHnjpbZegRcqmNC-sZNuZ8cizkwRMNEOMrP2WWDnha--sEdIxQCIB9MyLS95RIQBeQra49yxgxUorRDm-_hiBoU4uC-jld4DcflXH1tQDrfC2fJ5A8',
     'width': 637}],
   'place_id': 'ChIJfUtQfZg5jkcRsdkNm90qosc',
   'reference': 'CoQBewAAAKFKa5tCxwkd_FP6ISDNr4A7E1_RhoKiwl2yirjUooj2mTEM6D1x0Pgm6bN4Xvp3wqRt4NHNnBblwQNg4IFNnUt177-YNsHQS7yCb-SAlxrWlReENP-NToI9Snrqe2n8TP9fM7RaCa8I6GixOzI91OHESHUEN2lapT7Cc6sQrVZBEhATp_CofCOmhjfigMkp9CONGhQXb5XVfyyMuRJo2F3FhD11Qc0y7w',
   'types': ['restaurant', 'food', 'establishment']}],
 'status': 'OK'}

Place details - get more information once we have a place

Once we have successfully looked up a place, we will have an ID for it. This is Google's way of distinguishing between places of the same name, so that we know we have the right one. We can use that ID to get information about a place we have already looked up with the details API function.


In [3]:
search_result = r.json()

details_endpoint = 'https://maps.googleapis.com/maps/api/place/details/json'
dparams = {
    'key': 'AIzaSyCNx-klDCfhopV6W_QPFZ0iwv5sp1J0XwQ',
    'placeid': search_result['results'][0]['place_id'],
    'language': 'en'
}
r = requests.get( details_endpoint, params=dparams)
r.json()  # See what we got


Out[3]:
{'html_attributions': [],
 'result': {'address_components': [{'long_name': '46',
    'short_name': '46',
    'types': ['street_number']},
   {'long_name': 'Muesmattstrasse',
    'short_name': 'Muesmattstrasse',
    'types': ['route']},
   {'long_name': 'Muesmatt',
    'short_name': 'Muesmatt',
    'types': ['sublocality_level_2', 'sublocality', 'political']},
   {'long_name': 'Länggasse-Felsenau',
    'short_name': 'Länggasse-Felsenau',
    'types': ['sublocality_level_1', 'sublocality', 'political']},
   {'long_name': 'Bern',
    'short_name': 'Bern',
    'types': ['locality', 'political']},
   {'long_name': 'Bern',
    'short_name': 'Bern',
    'types': ['administrative_area_level_2', 'political']},
   {'long_name': 'Bern',
    'short_name': 'BE',
    'types': ['administrative_area_level_1', 'political']},
   {'long_name': 'Switzerland',
    'short_name': 'CH',
    'types': ['country', 'political']},
   {'long_name': '3012', 'short_name': '3012', 'types': ['postal_code']}],
  'adr_address': '<span class="street-address">Muesmattstrasse 46</span>, <span class="postal-code">CH-3012</span> <span class="locality">Bern</span>, <span class="country-name">Switzerland</span>',
  'formatted_address': 'Muesmattstrasse 46, 3012 Bern, Switzerland',
  'formatted_phone_number': '031 301 62 22',
  'geometry': {'location': {'lat': 46.954447, 'lng': 7.429129}},
  'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png',
  'id': '2388120ff47e1457ecd9b06c87e017e6d4e62683',
  'international_phone_number': '+41 31 301 62 22',
  'name': 'Restaurant Länggass Stübli da Massimo',
  'opening_hours': {'open_now': True,
   'periods': [{'close': {'day': 1, 'time': '2300'},
     'open': {'day': 1, 'time': '0800'}},
    {'close': {'day': 2, 'time': '2300'}, 'open': {'day': 2, 'time': '0800'}},
    {'close': {'day': 3, 'time': '2300'}, 'open': {'day': 3, 'time': '0800'}},
    {'close': {'day': 4, 'time': '2300'}, 'open': {'day': 4, 'time': '0800'}},
    {'close': {'day': 5, 'time': '2300'}, 'open': {'day': 5, 'time': '0800'}},
    {'close': {'day': 6, 'time': '2300'}, 'open': {'day': 6, 'time': '1800'}}],
   'weekday_text': ['Monday: 8:00 am – 11:00 pm',
    'Tuesday: 8:00 am – 11:00 pm',
    'Wednesday: 8:00 am – 11:00 pm',
    'Thursday: 8:00 am – 11:00 pm',
    'Friday: 8:00 am – 11:00 pm',
    'Saturday: 6:00 – 11:00 pm',
    'Sunday: Closed']},
  'photos': [{'height': 416,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAAOJnTk-m4JRkDttRg_xz493DNZIBSXja6E5rEwgg8optMb_piOB6bc5DcY9FtgQj_cKsF_xnUa6cs6gem6YS4bCfk5qHXA9A9WB5gqFO94MAwCx0OAjeWESL8Z82y9nHY3YHRWvVY7dxsDRzzdbPiThIQUGqymJ6TGWFZxoQFZE91xBoUj4PR_Ozs6HT1g8amL2e0Cx3l2tg',
    'width': 637},
   {'height': 337,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAA4Yv22kYBM8wKIA6qfL5Mxe7L0GtHATL9gtH2_zjANvum6g4_U3h_cJKLNmxj2domUTAyu-ZCPDAaiYyraJGr5m9Iyrwn-kd-TbPYGYI9gwh4Aqf38zFHHsarYngRsRraGdObtBTFsN7ua9xgRJ3vihIQq_R5KXs68DPoente3MWF0hoU2U_603YkLCZa2ALs-LyzSX1Ei5Q',
    'width': 726},
   {'height': 478,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAAEbynGDy5Wg_dmiVmwj1ekmpoGFMoIF8L78rVJ1QDKTPdKWCDcIstbKzMwJeiwoFNoKYzMCgTAmd6AYXECb5Tw07v-aCxaiRPMel1jL09-0wCiXvF_kUqydNNS4c7wOSfq9v3oGsVgCK7crXtpztc2BIQSLNzFuONVUry8NVHZJ2ZChoUDTJYgCdjC7uK3X0Y6krsQOVKdEs',
    'width': 637},
   {'height': 287,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAA75M0ompU3NZDvNX95iktPYatKYi0nISzsyYuGeBDy6RQruATJngvuqQwaajgk42XZ8ymoenXnD8jayj7ij-8ExkLjW2915B5ZQv8rbjCQZSeEPA3ETpY-dXZeLNJnRMAFOtv75EYrmbqXFgEhhxeuxIQeqc98DPPkz15FGT3qrMjeRoUa_koadN_H5jDp-iGEbyIii_FStI',
    'width': 314},
   {'height': 203,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAAGYnA2uzd2twuKluUf9_-mPakO1LM6HcPYvrE5LCiTRHqPgZWyjya0h57COwT05mSyABwmMhA-K9vS1KQr4cKhAugcy0nRV33Y-OKzbXJv1xG4yf4_dcqykJoNZWQod0yKewoeExdGCyljq4oCiCftxIQz3MXmt7rFwEq1ULMSXTDRhoUjzwudtuj1-eo7jxKbTuwOXsBs6E',
    'width': 183},
   {'height': 418,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAA8ALksa-b4K5d9G_LdkMyp_BWl6jxq39wXcw8QfFKXzOx6_DPzGKhVT7cv_wYqFb3pY_ymLFmWbPG62suY9a6Ln8rR42Ql6F2YI42TfnXqKN2cdxxk6KiNRQ1itE0DH1I7y3yt1_dKTr3RC9zMY70VhIQXAAGupkodfDXee5nP5dQXBoUOXfW5HB0_KJ12gz10nrsb7YFqOk',
    'width': 637},
   {'height': 522,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAAKb707Xr718sA8Kq432jA_lE00NFYs87m3PVS-SURr1qrtEMhZr6d3GCw7SazPfc-mcDRjHXD__UTOngaI3tO2kC7PHKBUA3qtFtpLha_QvlH8mXHKHBwSUwRJw6BwSPNO4i-QsfrebbO6BgvE6PRGhIQyHNX82iLKQiGt3b8yLEWehoUj5x0f64B2cIMd-2PYMUQl1PkBOs',
    'width': 378},
   {'height': 534,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAAD0sav7zd5SdExW44sj__f7uy-_tfgsEADyzyoHUAktCt4sN2Cixs-qtj4_fpI3sPrOmcAFKjhROn7hyVpweD4WFwJnlj5dhlSInkbWIcDFhXqzhxsEJPwj7g4CtIosDPYl34K8WhAh8V1egPb5Bu6RIQ2apPX5Fb5yPi6KZrXHF4nRoUyCHJx5-3n3_2OJPRJR2N3vaKg4o',
    'width': 713},
   {'height': 190,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAANWrhcBe8Go5qYX04xPOcvb_YtddlDg9u7hF4AtI3I2J3t2fZ7vZGXKf8ZykzsEuxkA85VWPC0s3955U8h7ryNd_1xKww5SdOVhz37NQCZLrbkQwzu5090wJPVW0UHkVmE5rKh7kafNl4x_4gu1iJchIQ_F7mV_UNZakLXZ6Mvu7OeRoUl6B2g8fmPWWIcnALOcACFDHdWhw',
    'width': 178},
   {'height': 396,
    'html_attributions': [],
    'photo_reference': 'CnRnAAAA0NPHhjYZZuqdZ81VAJ3o6Fqqrhjy2Yg5KlDZBFnlpqSFdQ2jGnkhk406Ss86nhNIM0ZJhFEKrAqCszCE3v2IqdsKsYDc0LwnUeps9viOf6sNkB9aUss1gU1deW3A0KqLQM89auwJU3KN6T_pZjaSnBIQLXHvfBmKaAp6B6NRUH_MtBoU1DyRt49ztHIpkjRINri7bMmmBGQ',
    'width': 727}],
  'place_id': 'ChIJfUtQfZg5jkcRsdkNm90qosc',
  'reference': 'CoQBewAAAKzUiADWKWthe8O2gttoJYxxaF6Fm65O-qkq192imRLTg1GJKkntxQw_UQ05BJ9gfR1dPslFX75D0tYY2CuM8sOi0AS08EHG6Ib69MKwGwss3Bkj6XBCy4iE02O5vrL7akwCCzFCCdBREsSOwP5qvrSg_It5Tt-jbZElZuA9JdnZEhCIKDn4SKBH7TnisTUTqaQpGhQ5zznwST-PUdwOHh5hDCT3t_80wQ',
  'reviews': [{'aspects': [{'rating': 3, 'type': 'overall'}],
    'author_name': 'A Google User',
    'rating': 5,
    'text': '',
    'time': 1291186930}],
  'scope': 'GOOGLE',
  'types': ['restaurant', 'food', 'establishment'],
  'url': 'https://plus.google.com/103880176915790775728/about?hl=en',
  'user_ratings_total': 1,
  'utc_offset': 120,
  'vicinity': 'Muesmattstrasse 46, Bern',
  'website': 'http://www.laenggass-stuebli.ch/'},
 'status': 'OK'}

Now let's look up a series of places! We'll store our results in places_found, for each place that we find.


In [4]:
places_to_lookup = ['Moskva', 'Venice', 'Rosslyn Chapel', 'Cantabrigia']
places_found = {}

for p in places_to_lookup:
    myparams = {
        'query': p,
        'key': 'AIzaSyCNx-klDCfhopV6W_QPFZ0iwv5sp1J0XwQ',
        'language': 'en'
        }
    myr = requests.get( search_endpoint, params=myparams )
    myresult = myr.json()
    if 'results' in myresult and len(myresult['results']) > 0:
        print("Found information for %s" % p)
        places_found[p] = myresult['results'][0]


Found information for Moskva
Found information for Venice
Found information for Rosslyn Chapel
Found information for Cantabrigia

In [5]:
places_found


Out[5]:
{'Cantabrigia': {'formatted_address': 'Cambridge, Cambridge, UK',
  'geometry': {'location': {'lat': 52.205337, 'lng': 0.121817},
   'viewport': {'northeast': {'lat': 52.237855, 'lng': 0.1919273},
    'southwest': {'lat': 52.1598292, 'lng': 0.048047}}},
  'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png',
  'id': '88043868ee98da74dc38e08ddc15298630c4660f',
  'name': 'Cambridge',
  'place_id': 'ChIJLQEq84ld2EcRIT1eo-Ego2M',
  'reference': 'CoQBdQAAADjE98-A5XyxGGkSeZ_6sGF_e222QBJJITcuLD7GnAJpH6E5oMMdhQLGEIkOYfdcWnkc4frLASLXDLqI8cJ7jACqccPv3YkYoYcYjpRDZcCu5zh5QU4TTMS1_mxfEAQQ5Qxx28LB1-yr9E638cTUgVSK1YvGHfA-RENRisUyj9F1EhCBuz4FZmY7BW3FXIZ1RZdQGhSpZkgtvW_2UrGIYc9K99503aQunw',
  'types': ['locality', 'political']},
 'Moskva': {'formatted_address': 'Moscow, Russia',
  'geometry': {'location': {'lat': 55.755826, 'lng': 37.6173},
   'viewport': {'northeast': {'lat': 56.009657, 'lng': 37.9456611},
    'southwest': {'lat': 55.48992699999999, 'lng': 37.3193288}}},
  'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png',
  'id': '1a0f08fcbc047354782f00ab52e66fb56d1aadf7',
  'name': 'Moscow',
  'place_id': 'ChIJybDUc_xKtUYRTM9XV8zWRD0',
  'reference': 'CnRrAAAAqL46T2VEqVcwazzh6f9RPj7AXPpXn3Z2TLzGS_kt_VMZ9ahTmWm7_ssdVpRZy4BPzyFLR-8ZwzR9RSsswYalX2z2mnrvu2ztkST5y6n7aCcr14l8GLrk-OEF5lBq9nx5VwWxyJT6wPlhjnDOWy_9uxIQCroVp6dCRg4WkclVc1hM7hoUfu2mMpt-mbp9vusSBzOK8xLetq8',
  'types': ['locality', 'political']},
 'Rosslyn Chapel': {'formatted_address': 'Chapel Loan, Roslin, Midlothian EH25 9PU, United Kingdom',
  'geometry': {'location': {'lat': 55.855378, 'lng': -3.160194}},
  'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/worship_general-71.png',
  'id': 'b6072ac068df85d76ed322026d69e2e2cf8c236f',
  'name': 'Rosslyn Chapel',
  'opening_hours': {'open_now': False, 'weekday_text': []},
  'photos': [{'height': 2048,
    'html_attributions': ['<a href="https://plus.google.com/102502886994109905990">Donny Hammell</a>'],
    'photo_reference': 'CoQBdwAAANIYQEb8NEdczoolSN_VtG9-4k63MzRcDqA9A2tj3HEHNpF5PmH0QNFLzn3mKyKt1ZgkwwwkjptARHcnTjqjn0dnOrTEAZfrcit3Ry1i7NUCrc01eDMRk9xrtzpQNC9t6j1uVdhNLFAO9Z5PqKVyXbvqjs3hIoXqddBNko2Eh-lxEhBehrbwtsBsx0OQyfLRSNprGhSgOhXKrsffoNevx7VWts5JM4UGNg',
    'width': 1365}],
  'place_id': 'ChIJgc6n132_h0gR1hg3dCGiY9M',
  'rating': 4.8,
  'reference': 'CnRiAAAA0H28JSOciVyvDQg-r7GTqWBhE1NeiIKxx0dOSMqL2E9FgJb1SBzHqTwDKQ8yThfERKVQiacCX9FXtHEyvhrTTBL4yMkQVQf6OL7fsRcijA5VtnO56RTYh-kyHFH2hFmezD1UuDpoS31XqiPrGaOjJhIQPHR3upBhh1M7GJlXIuyNHRoU65B2gg8O2RIfVLM29JsuAl2zrHk',
  'types': ['church', 'place_of_worship', 'establishment']},
 'Venice': {'formatted_address': 'Venice, Italy',
  'geometry': {'location': {'lat': 45.4408474, 'lng': 12.3155151},
   'viewport': {'northeast': {'lat': 45.5779746, 'lng': 12.5966574},
    'southwest': {'lat': 45.233455, 'lng': 12.1668278}}},
  'icon': 'http://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png',
  'id': '350e3bc6f276aed698cd4b23915466b94e06c3ee',
  'name': 'Venice',
  'place_id': 'ChIJiT3W8dqxfkcRLxCSvfDGo3s',
  'reference': 'CnRqAAAA57Ezjxp2MD8KGLplGH5NCAVqt0wcq5L92cEvPgGwvfjb6SCbNMV000r6GvNzryADKW03U0ybGj2l3Wbkg7MffHpXFoftEDW344ZP8eIBR3Riy_N6EVzpwI4WHluc1170ndfr9GML2po0XGkk0Stq6RIQFd6hQE-6R47pc2wl_t4gShoURCSkuf_0aC2Rabt3UDbMozxwwUY',
  'types': ['locality', 'political']}}

Exporting our data to CSV

One thing we can do with the Places API is to look up a bunch of places, get their latitude and longitude or their canonical names, and put those into a big spreadsheet for use elsewhere (or even for importing into Google Maps to make a map!)

The easiest way to make something like a spreadsheet in a computer program is to use CSV, which stands for comma separated values. That is what we used earlier to get our UK fat supply data into our map. Python has a built-in module for this, and we use it like this to make a CSV file.


In [6]:
import csv

f = open('myplaces.csv', 'w', newline='', encoding='utf-8')
writer = csv.writer(f)
# First, write our column headers!
writer.writerow(['Place name', 'Address', 'ID', 'Latitude', 'Longitude'])


Out[6]:
42

Now we have an open file called 'myplaces.csv', and we have written one row to it. If you were to close the filehandle now and look at the file, you would see that it looks like this:

Place name,Address,ID,Latitude,Longitude

But we won't close the file yet, because we want to write each of our places into its row.


In [7]:
for p in places_found.keys():
    place_info = places_found[p]
    address = place_info['formatted_address']
    placeid = place_info['place_id']
    latitude = place_info['geometry']['location']['lat']
    longitude = place_info['geometry']['location']['lng']
    writer.writerow([p, address, placeid, latitude, longitude])

f.close()  # Always close what you open, if you didn't use 'with'!

Now we can make sure the file is there and has what we expect!


In [8]:
with open('myplaces.csv', encoding='utf-8') as f:
    data = f.read()
    
print(data)


Place name,Address,ID,Latitude,Longitude
Cantabrigia,"Cambridge, Cambridge, UK",ChIJLQEq84ld2EcRIT1eo-Ego2M,52.205337,0.121817
Rosslyn Chapel,"Chapel Loan, Roslin, Midlothian EH25 9PU, United Kingdom",ChIJgc6n132_h0gR1hg3dCGiY9M,55.855378,-3.160194
Moskva,"Moscow, Russia",ChIJybDUc_xKtUYRTM9XV8zWRD0,55.755826,37.6173
Venice,"Venice, Italy",ChIJiT3W8dqxfkcRLxCSvfDGo3s,45.4408474,12.3155151

And now it is ready for processing anywhere you like, including putting back into Google Maps if you so choose.